#pragma once


//////////////////////////////////////////////////////////////////////////
// Include the vector type.
#include <Math/Vector3fv.hpp>


//////////////////////////////////////////////////////////////////////////
// Use a typed fixture to make the tests easier to duplicate.
template< typename Implementation >
class Vector3f_Test : public ::testing::Test
{
public:
	// Definitions.
	typedef Implementation									Base_t;
	typedef typename Math::Vector3f_Simd< Base_t >			Vector_t;

	// Overrides from testing::Test.
	void					SetUp()							{}
	void					TearDown()						{}

	static bool				VectorEqual( const Vector_t& lhs, const Vector_t& rhs )
	{
		// Simple, dirty and not very good.
		Vector_t	delta	=	lhs - rhs;
		return delta.X()<0.01f && delta.Y()<0.01f && delta.Z()<0.01f;
	}
};


//////////////////////////////////////////////////////////////////////////
// Make a list of the types we want.
typedef testing::Types<
	Math::Simd::Reference
#ifdef SIMD_SSE
	,	Math::Simd::Sse
#endif
#ifdef SIMD_SSE_3
	,	Math::Simd::Sse3
#endif
#ifdef SIMD_SSE_4
	,	Math::Simd::Sse4
#endif
#ifdef SIMD_SSE_AVX
	,	Math::Simd::Avx
#endif
#ifdef SIMD_NEON
	,	Math::Simd::Neon
#endif
>	TypeImpls;


//////////////////////////////////////////////////////////////////////////
// Define the test case types to be used.
TYPED_TEST_CASE( Vector3f_Test, TypeImpls );


//////////////////////////////////////////////////////////////////////////
// Define the various tests.

//////////////////////////////////////////////////////////////////////////
// Test the basic construction, by side effect it
// also needs to test the member accessors.
TYPED_TEST( Vector3f_Test, BasicConstruction )
{
	typedef typename Vector3f_Test< TypeParam >::Vector_t	Vector_t;

	Vector_t	xAxis( Vector_t::XAxis() );
	EXPECT_FLOAT_EQ( 1.0f, xAxis.X() );
	EXPECT_FLOAT_EQ( 0.0f, xAxis.Y() );
	EXPECT_FLOAT_EQ( 0.0f, xAxis.Z() );

	Vector_t	yAxis( Vector_t::YAxis() );
	EXPECT_FLOAT_EQ( 0.0f, yAxis.X() );
	EXPECT_FLOAT_EQ( 1.0f, yAxis.Y() );
	EXPECT_FLOAT_EQ( 0.0f, yAxis.Z() );

	Vector_t	zAxis( Vector_t::ZAxis() );
	EXPECT_FLOAT_EQ( 0.0f, zAxis.X() );
	EXPECT_FLOAT_EQ( 0.0f, zAxis.Y() );
	EXPECT_FLOAT_EQ( 1.0f, zAxis.Z() );
}


// Test accessor assignments.
TYPED_TEST( Vector3f_Test, BasicInsertions )
{
	typedef typename Vector3f_Test< TypeParam >::Vector_t	Vector_t;
	
	Vector_t	zero( 4.0f, 5.0f, 6.0f );

	zero.X( 1.0f );
	EXPECT_FLOAT_EQ( 1.0f, zero.X() );
	EXPECT_FLOAT_EQ( 5.0f, zero.Y() );
	EXPECT_FLOAT_EQ( 6.0f, zero.Z() );

	zero.Y( 2.0f );
	EXPECT_FLOAT_EQ( 1.0f, zero.X() );
	EXPECT_FLOAT_EQ( 2.0f, zero.Y() );
	EXPECT_FLOAT_EQ( 6.0f, zero.Z() );

	zero.Z( 3.0f );
	EXPECT_FLOAT_EQ( 1.0f, zero.X() );
	EXPECT_FLOAT_EQ( 2.0f, zero.Y() );
	EXPECT_FLOAT_EQ( 3.0f, zero.Z() );
}


// Test validation.
TYPED_TEST( Vector3f_Test, IsValid )
{
	typedef typename Vector3f_Test< TypeParam >::Vector_t	Vector_t;

	Vector_t	one( 1.0f, 1.0f, 1.0f );

	// NOTE: VC is rather clever and notices potential division by zero,
	// so we use math to zero out the value so the compiler doesn't bitch.
	Vector_t	zero	= Vector_t::SimdType_t::Mul( one, Vector_t( 0.0f, 0.0f, 0.0f ) );

	EXPECT_TRUE( one.IsValid() );
	EXPECT_TRUE( zero.IsValid() );

	one		=	Vector_t::SimdType_t::Div( one, zero );

	EXPECT_FALSE( one.IsValid() );
}

// Test the basic negation.
TYPED_TEST( Vector3f_Test, Negation )
{
	typedef typename Vector3f_Test< TypeParam >::Vector_t	Vector_t;

	Vector_t	val( 1.0f, 2.0f, 3.0f );
	val	=	-val;

	EXPECT_FLOAT_EQ( -1.0f, val.X() );
	EXPECT_FLOAT_EQ( -2.0f, val.Y() );
	EXPECT_FLOAT_EQ( -3.0f, val.Z() );
}


// Test basic addition.
TYPED_TEST( Vector3f_Test, Addition )
{
	typedef typename Vector3f_Test< TypeParam >::Vector_t	Vector_t;

	EXPECT_TRUE( this->VectorEqual( Vector_t( 2.0f, 3.0f, 4.0f ), Vector_t( 1.0f, 1.5f, 2.0f ) + Vector_t( 1.0f, 1.5f, 2.0f ) ) );
}


// Test basic subtraction.
TYPED_TEST( Vector3f_Test, Subtraction )
{
	typedef typename Vector3f_Test< TypeParam >::Vector_t	Vector_t;

	EXPECT_TRUE( this->VectorEqual( Vector_t( 1.0f, 2.0f, 3.0f ), Vector_t( 10.0f, 8.0f, 5.0f ) - Vector_t( 9.0f, 6.0f, 2.0f ) ) );
}


// Test scalar multiplication and division.
TYPED_TEST( Vector3f_Test, Scalar )
{
	typedef typename Vector3f_Test< TypeParam >::Vector_t	Vector_t;

	EXPECT_TRUE( this->VectorEqual( Vector_t( 2.0f, 0.0f, 0.0f ), Vector_t::XAxis()*2.0f ) );
	EXPECT_TRUE( this->VectorEqual( Vector_t( 2.0f, 0.0f, 0.0f ), 2.0f*Vector_t::XAxis() ) );
	EXPECT_TRUE( this->VectorEqual( Vector_t( 0.5f, 0.0f, 0.0f ), Vector_t::XAxis()/2.0f ) );
}


// Test the addition assignment.
TYPED_TEST( Vector3f_Test, AddAssign )
{
	typedef typename Vector3f_Test< TypeParam >::Vector_t	Vector_t;

	Vector_t	test( 1.0f, 1.5f, 2.0f );
	test	+=	Vector_t( 1.0f, 1.5f, 2.0f );

	EXPECT_TRUE( this->VectorEqual( Vector_t( 2.0f, 3.0f, 4.0f ), test ) );
}


// Test the subtraction assignment.
TYPED_TEST( Vector3f_Test, SubAssign )
{
	typedef typename Vector3f_Test< TypeParam >::Vector_t	Vector_t;

	Vector_t	test( 10.0f, 8.0f, 5.0f );
	test	-=	Vector_t( 9.0f, 6.0f, 2.0f );

	EXPECT_TRUE( this->VectorEqual( Vector_t( 1.0f, 2.0f, 3.0f ), test ) );
}


// Test the scalar assignments.
TYPED_TEST( Vector3f_Test, ScaleAssign )
{
	typedef typename Vector3f_Test< TypeParam >::Vector_t	Vector_t;

	Vector_t	test( Vector_t::XAxis() );
	test	*=	2.0f;

	EXPECT_TRUE( this->VectorEqual( Vector_t( 2.0f, 0.0f, 0.0f ), test ) );

	test	=	Vector_t::XAxis();
	test	/=	2.0f;

	EXPECT_TRUE( this->VectorEqual( Vector_t( 0.5f, 0.0f, 0.0f ), test ) );
}


// Test the magnitude helper.
TYPED_TEST( Vector3f_Test, Magnitude )
{
	typedef typename Vector3f_Test< TypeParam >::Vector_t	Vector_t;

	Vector_t	val( 1.0f, 2.0f, 3.0f );
	EXPECT_FLOAT_EQ( std::sqrt( 14.0f ), Math::Magnitude( val ) );
}


// Test dot product calculation.
TYPED_TEST( Vector3f_Test, Dot )
{
	typedef typename Vector3f_Test< TypeParam >::Vector_t	Vector_t;
	
	Vector_t	lhs( 1.0f, 2.0f, 3.0f );
	Vector_t	rhs( 4.0f, 5.0f, 6.0f );
	EXPECT_FLOAT_EQ( 32.0f, lhs * rhs );
}


// Test cross product calculation.
TYPED_TEST( Vector3f_Test, Cross )
{
	typedef typename Vector3f_Test< TypeParam >::Vector_t	Vector_t;

	Vector_t	result	=	Math::Cross( Vector_t::ZAxis(), Vector_t::XAxis() );
	EXPECT_TRUE( this->VectorEqual( Vector_t::YAxis(), result ) );

	result	=	Math::Cross( Vector_t::YAxis(), Vector_t::ZAxis() );
	EXPECT_TRUE( this->VectorEqual( Vector_t::XAxis(), result ) );

	result	=	Math::Cross( Vector_t::XAxis(), Vector_t::YAxis() );
	EXPECT_TRUE( this->VectorEqual( Vector_t::ZAxis(), result ) );
}
